package org.koin.experimental.builder;

import org.koin.core.error.ClosedScopeException;
import org.koin.core.error.DefinitionParameterException;
import org.koin.core.error.NoBeanDefFoundException;
import org.koin.core.logger.Level;
import org.koin.core.logger.Logger;
import org.koin.core.scope.Scope;
import org.koin.ext.ClassUtil;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;

public class InstanceBuilder {

    public static <T> T create(Scope scope, Class<T> clazz)
            throws DefinitionParameterException,
            NoBeanDefFoundException,
            ClosedScopeException {

        Logger logger = scope.getLogger();

        if (logger.isAt(Level.DEBUG)) {
            logger.debug("!- creating class:" + ClassUtil.getFullName(clazz) + "");
        }

        Constructor<?> constructor = null;
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> item : constructors) {
            if (item != null) {
                constructor = item;
                break;
            }
        }

        if (constructor == null) {
            throw new IllegalStateException("No constructor found for class '"
                    + ClassUtil.getFullName(clazz) + "'");
        }

        Object[] args;

        if (logger.isAt(Level.DEBUG)) {
            long start = System.nanoTime();

            args = getArguments(constructor, scope);

            long end = System.nanoTime();
            DecimalFormat to = new DecimalFormat("0.0000");
            String duration = to.format((end - start) / 1000000.0);
            logger.debug("!- got arguments in " + duration + " ms");
        } else {
            args = getArguments(constructor, scope);
        }

        T instance;

        if (logger.isAt(Level.DEBUG)) {
            long start = System.nanoTime();

            instance = (T) createInstance(args, constructor);

            long end = System.nanoTime();
            DecimalFormat to = new DecimalFormat("0.0000");
            String duration = to.format((end - start) / 1000000.0);
            logger.debug("!- created instance in " + duration + " ms");
        } else {
            instance = (T) createInstance(args, constructor);
        }

        return instance;
    }

    public static Object createInstance(Object[] args, Constructor constructor) {
        if (args.length == 0) {
            try {
                return constructor.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } else {
            try {
                return constructor.newInstance(args);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static Object[] getArguments(Constructor<?> constructor, Scope context)
            throws DefinitionParameterException,
            NoBeanDefFoundException,
            ClosedScopeException {
        int length = constructor.getParameterTypes().length;
        if (length == 0) {
            return new Object[0];
        }
        Object[] result = new Object[length];
        for (int i = 0; i < length; i++) {
            Class p = constructor.getParameterTypes()[i];
            result[i] = context.get(p);
        }
        return result;
    }
}
